package com.googlecode.androidkurskoblenz.jledcube;

import java.io.BufferedReader;
import java.io.BufferedWriter;
import java.io.IOException;
import java.io.InputStream;
import java.io.InputStreamReader;
import java.io.OutputStream;
import java.io.OutputStreamWriter;
import java.util.logging.ConsoleHandler;
import java.util.logging.FileHandler;
import java.util.logging.Handler;
import java.util.logging.Level;
import java.util.logging.Logger;
import java.util.logging.SimpleFormatter;

import javax.bluetooth.RemoteDevice;
import javax.bluetooth.UUID;
import javax.microedition.io.Connector;
import javax.microedition.io.StreamConnection;
import javax.microedition.io.StreamConnectionNotifier;

import com.googlecode.androidkurskoblenz.threedcube.ThreeDLedCube;

/**
 * JLedServer
 * Listens for connections via bluetooth
 * and starts the LedCube
 *
 * Reads changing leds and informs the cube.
 */
public class JLedServer
{
    public static void main(String[] args)
    {
        // Autostart JLedServer
        new JLedServer();
    }

    private StreamConnection connection;
    private RemoteDevice dev;
    private final Logger log = Logger.getLogger(JLedServer.class.getName());
    private StreamConnectionNotifier scn;
    private final String sdpname = "BluetoothCube";

    private final UUID uuid = new UUID("0000110100001000800000805F9B34FB", false);

    /**
     * Initializes the server.
     * Creating log file in /tmp/server.log -> Linux!
     */
    public JLedServer()
    {
        // Set logging level
        Handler handler = new ConsoleHandler();
        handler.setLevel(Level.FINEST);
        this.log.addHandler(handler);
        try
        {
            handler = new FileHandler("/tmp/server.log", 20971520, 5, true);
            handler.setLevel(Level.FINEST);
            handler.setFormatter(new SimpleFormatter());
            this.log.addHandler(handler);
        }
        catch(SecurityException e)
        {
            e.printStackTrace();
        }
        catch(IOException e)
        {
            e.printStackTrace();
        }
        this.log.setLevel(Level.ALL);

        this.log.info("Open bluetooth server...");
        try
        {
            this.scn = (StreamConnectionNotifier) Connector.open("btspp://localhost:" + this.uuid + ";name=" + this.sdpname);
        }
        catch(IOException ioe)
        {
            this.log.severe("Could not open connection");
            System.exit(255);
            // Do nothing !?
        }

        do
        {
            this.accept();
            this.start();
            this.log.info("Connection to device closed.");
        }
        while(true);
    }

    public void accept()
    {
        // Wait for client server
        this.log.info("Waiting for client to connect on localhost ...");

        try
        {
            this.connection = this.scn.acceptAndOpen();
        }
        catch(IOException ioe)
        {
            // Do Nothing
        }

        // Remote Device found
        this.log.info("Connection established. Fetching remote device.");
        try
        {
            this.dev = RemoteDevice.getRemoteDevice(connection);
            this.log.info("Connected with " + dev.getFriendlyName(true) + " on " + dev.getBluetoothAddress());
        }
        catch(IOException ioe)
        {
            this.log.severe("Could not get remote device information.");
        }

    }

    public void start()
    {
        BufferedReader bReader;
        BufferedWriter bWriter;
        try
        {
            InputStream inStream = this.connection.openInputStream();
            bReader = new BufferedReader(new InputStreamReader(inStream));
            OutputStream outStream = this.connection.openOutputStream();
            bWriter = new BufferedWriter(new OutputStreamWriter(outStream));

            // Create cube
            LedCube lc = new SimpleLedCube();

            // Sending Width / Depth / Height to android device
            bWriter.write(String.format("%c%c%c%n", lc.getWidth(), lc.getDepth(), lc.getHeight()));
            bWriter.flush();

            char data;
            int z = 0;
            int y = 0;
            int x = 0;
            boolean run = true;

            while(run)
            {
                try
                {
                    data = (char) bReader.read();
                    z = -1;
                    y = -1;
                    x = -1;
                    // Reset on every layer instead of single led
                    if(Character.toLowerCase(data) == 'l')
                    {
                        String levelString = "";
                        do
                        {
                            data = (char) bReader.read();
                            if(data != ',')
                            {
                                levelString += data;
                            }
                        }
                        while(data != ',');
                        z = Integer.parseInt(levelString);
                        y = 0;
                        x = 0;

                        data = (char) bReader.read();
                        while(data == '0' || data == '1')
                        {
                            this.log.fine(
                                    "Data: " + String.valueOf(data) +
                                    " Z: " + String.valueOf(z) +
                                    " Y: " + String.valueOf(y) +
                                    " X: " + String.valueOf(x)
                                    );
                            lc.changeEnable(z, x, y, data == '1');

                            x++;
                            if (x >= lc.getWidth())
                            {
                                x = 0;
                                y++;
                            }
                            if (y >= lc.getDepth())
                            {
                                y = 0;
                            }

                            data = (char) bReader.read();
                        }
                    }
                    // Reset single led with x,y,z
                    else if(Character.toLowerCase(data) == 's')
                    {
                        z = bReader.read();
                        y = bReader.read(); // Because client has left rotating we have to change
                        x = bReader.read();
                        int enable = (int) bReader.read(); // LED on or off
                        this.log.fine(
                                "Data: " + String.valueOf(data) +
                                " Z: " + String.valueOf(z) +
                                " Y: " + String.valueOf(x) +
                                " X: " + String.valueOf(y) +
                                " Enable: " + String.valueOf(enable)
                                );
                        if (enable == 0) {
                            lc.changeEnable(z, x, y, false);
                        } else if (enable == 1) {
                            lc.changeEnable(z, x, y, true);
                        }
                    }
                    else if(data == '\n')
                    {
                        this.log.finest("Got newline");
                    }
                    else if(((int) data) == 65535)
                    {
                        run = false;
                    }
                    else
                    {
                        this.log.fine(
                                "Unknown Data: " + ((int) data)
                                );
                    }
                }
                catch(IOException ioe)
                {
                    this.log.severe("Could not read or write to device.");
                }
            }
        }
        catch(IOException ioe)
        {
            this.log.severe("Could not open input / output writing... ");
        }

    }
}
